# -*- coding: utf-8 -*-
# @Author: Lp
# @File: base_page.py
import os
import time
import allure
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common import TimeoutException, NoSuchElementException, \
    StaleElementReferenceException, ElementNotInteractableException,WebDriverException
from po.exceptions import ElementPlaceException, ElementClickException, ElementInputException, \
    ElementSwipeException, ElementKeyboardException, ElementAssertionException


class BasePage:
    def __init__(self, driver):
        self.driver = driver
        self.timeout = 6

    def _take_screenshot(self, name):
        """
        截图并添加到报告中
        param name: name (str): 截图名称
        """
        timestamp = time.strftime("%Y%m%d-%H%M%S")
        filename = f'{name}_{timestamp}.png'
        screenshot_path = os.path.abspath(f'./reports/{filename}')
        os.makedirs(os.path.dirname(screenshot_path), exist_ok=True)
        self.driver.save_screenshot(screenshot_path)
        allure.attach.file(source= screenshot_path,
                           name = name,
                           attachment_type = allure.attachment_type.PNG)
        return screenshot_path


    def _handle_generic_error(self, error_type, error_msg, original_exception,
                              custom_exception_class) -> None:
        """
        统一处理错误：截图、记录日志、附加报告并抛出专用异常
        Args:
            error_type (str): 错误类型标识（用于截图命名）
            error_msg (str): 自定义错误信息
            original_exception (Exception): 原始异常对象
            custom_exception_class (type): 要抛出的自定义异常类
        """
        # 1. 截图保存
        screenshot = self._take_screenshot(f"{error_type}_error")
        # 2. 构建完整错误信息
        full_error = (
            f"{error_msg}\n"
            f"截图路径: {screenshot}\n"
            f"原始错误: {str(original_exception)}"
        )
        # 3. 附加到Allure报告
        allure.attach(full_error, f"{error_type}详情", allure.attachment_type.TEXT)
        # 4. 抛出专用异常（保留原始异常栈）
        exception = custom_exception_class(full_error)
        exception.screenshot_path = screenshot
        if hasattr(original_exception, "msg"):
            exception.original_message = original_exception.msg
        if hasattr(original_exception, "stacktrace"):
            exception.stacktrace = original_exception.stacktrace
        raise exception from original_exception


    @allure.step('定位元素: {place}')
    def find_element(self, place, poll_frequency=0.5, timeout=None):
        """
        显示等待元素出现
        driver: WebDriver实例
        place: 元素定位信息，元组类型，如(By.ID, 'element_id')
        timeout: 超时时间，默认6秒
        poll_frequency: 检查间隔时间，默认0.5秒
        return: 找到的元素
        """
        try:
            wait_timeout = timeout or self.timeout

            return WebDriverWait(
                self.driver,
                wait_timeout,
                poll_frequency
            ).until(EC.presence_of_element_located(place))

        except TimeoutException as e:
            self._handle_generic_error("element_timeout",
                f"元素定位超时: {place} | 超时时间: {wait_timeout}秒",
                e,
                ElementPlaceException)

        except (NoSuchElementException, StaleElementReferenceException) as e:
            # 处理元素未找到或状态过期
            self._handle_generic_error(
                "element_not_found",
                f"元素未找到或状态过期: {place}",
                e,
                ElementPlaceException
            )

        except ElementNotInteractableException as e:

            self._handle_generic_error(
                "element_not_interactable",
                f"元素不可交互: {place} - 可能被遮挡或禁用",
                e,
                ElementPlaceException
            )

        except WebDriverException as e:

            self._handle_generic_error(
                "webdriver_error",
                f"WebDriver异常: {place} | 错误类型: {type(e).__name__}",
                e,
                ElementPlaceException
            )

        except Exception as e:

            self._handle_generic_error(
                "unknown_place_error",
                f"未知定位错误: {place} | 错误类型: {type(e).__name__}",
                e,
                ElementPlaceException
            )


    # 点击元素
    @allure.step('点击元素')
    def click_element(self, place):
        try:
            ele = self.find_element(place)
            ele.click()
            allure.attach(f"成功点击元素: {place}", "点击操作", allure.attachment_type.TEXT)
            return True

        except ElementPlaceException as e:

            self._handle_generic_error(
                "click_place_failure",
                f"点击失败-元素定位问题: {place}",
                e,
                ElementClickException
            )

        except ElementNotInteractableException as e:

            self._handle_generic_error(
                "element_not_clickable",
                f"元素不可点击: {place} - 可能被禁用或隐藏",
                e,
                ElementClickException
            )

        except WebDriverException as e:

            self._handle_generic_error(
                "click_webdriver_error",
                f"点击时WebDriver错误: {place} | 错误: {str(e)}",
                e,
                ElementClickException
            )

        except Exception as e:

            self._handle_generic_error(
                "unknown_click_error",
                f"未知点击错误: {place} | 错误类型: {type(e).__name__}",
                e,
                ElementClickException
            )



    # 输入元素内容
    @allure.step('在元素 {place} 中输入文本: {atext}')
    def input_text(self, place, atext, slow_type=True):
        try:
            ele = self.find_element(place)
            ele.clear()
            if slow_type:
                for char in atext:
                    ele.send_keys(char)
                    time.sleep(0.1)
            else:
                ele.send_keys(atext)
            return True

        except ElementPlaceException as e:

            self._handle_generic_error(
                "input_place_failure",
                f"输入失败-元素定位问题: {place}",
                e,
                ElementInputException
            )

        except ElementNotInteractableException as e:

            self._handle_generic_error(
                "element_not_editable",
                f"元素不可编辑: {place} - 可能被禁用或非输入框",
                e,
                ElementInputException
            )

        except WebDriverException as e:

            self._handle_generic_error(
                "input_webdriver_error",
                f"输入时WebDriver错误: {place} | 文本: '{atext}' | 错误: {str(e)}",
                e,
                ElementInputException
            )

        except Exception as e:

            self._handle_generic_error(
                "unknown_input_error",
                f"未知输入错误: {place} | 文本: '{atext}' | 错误类型: {type(e).__name__}",
                e,
                ElementInputException
            )


    @allure.step('页面向上滑动到顶部')
    def slide_top(self, max_attempts=3):
        """
        滑动到页面顶部
        max_attempts (int): 最大尝试次数，默认为3

        Raises:
            Exception: 滑动失败
        """
        try:
            # 获取屏幕尺寸
            window_size = self.driver.get_window_size()
            width = window_size['width']
            height = window_size['height']

            # 滑动起点和终点坐标
            start_x = width / 2
            start_y = height * 0.2  # 从屏幕底部80%位置开始
            end_x = width / 2
            end_y = height * 0.8  # 滑动到屏幕顶部20%位置

            for _ in range(max_attempts):
                # 执行滑动
                self.driver.swipe(
                    start_x, start_y,
                    end_x, end_y,
                    1000
                )
                time.sleep(1)

            return True

        except WebDriverException as e:

            self._handle_generic_error(
                "swipe_webdriver_error",
                f"滑动操作WebDriver错误 | 错误: {str(e)}",
                e,
                ElementSwipeException
            )

        except Exception as e:

            self._handle_generic_error(
                "unknown_swipe_error",
                f"未知滑动错误 | 错误类型: {type(e).__name__}",
                e,
                ElementSwipeException
            )

    @allure.step('页面向下滑动寻找元素: {place}')
    def scroll_to_element(self, place, max_swipes=5):
        """滑动直到元素可见"""
        window_size = self.driver.get_window_size()
        width = window_size['width']
        height = window_size['height']
        allure.attach(f"开始滑动查找元素: {place}", "滑动操作", allure.attachment_type.TEXT)
        for attempt in range(max_swipes):
            try:
                return self.find_element(place, timeout=2)
            except ElementPlaceException:

                self.driver.swipe(
                    start_x=width / 2,
                    start_y = height * 0.8,
                    end_x = width / 2,
                    end_y = height * 0.2,
                    duration=600
                )

                allure.attach(f"第 {attempt}/{max_swipes} 次滑动", "滑动进度", allure.attachment_type.TEXT)

        self._handle_generic_error(
            "element_not_found_after_scroll",
            f"滑动 {max_swipes} 次后仍未找到元素: {place}",
            None,
            ElementPlaceException
        )

    # 键盘回车
    @allure.step('键盘操作{action}')
    def enter_done(self, action="done"):
        """
              执行键盘操作
              :param action: 键盘动作类型 (done, search, go, next 等)
              """
        try:
            self.driver.execute_script(
                'mobile: performEditorAction',
                {'action': action}
            )
            return True

        except WebDriverException as e:
            self._handle_generic_error(
                f"keyboard_{action}_webdriver_error",
                f"键盘操作失败: {action} | WebDriver错误: {str(e)}",
                e,
                ElementKeyboardException
            )

        except Exception as e:
            self._handle_generic_error(
                f"keyboard_{action}_unknown_error",
                f"未知键盘操作错误: {action} | 错误类型: {type(e).__name__}",
                e,
                ElementKeyboardException
            )

    @allure.step('断言元素文本等于预期值')
    def assert_element_text(self, place , expected_text) -> None:
        """
        通用元素文本断言方法
        :param place: 元素定位器（如(By.ID, 'xxx')）
        :param expected_text: 预期的文本内容
        :raises ElementAssertionException: 断言失败时抛出带截图的异常
        """
        try:
            element = self.find_element(place)
            actual_text = element.text.strip()
            allure.attach(f"预期文本: {expected_text}", "断言详情", allure.attachment_type.TEXT)
            allure.attach(f"实际文本: {actual_text}", "断言详情", allure.attachment_type.TEXT)
            assert actual_text == expected_text, \
                f"文本断言失败 | 定位器: {place}"

        except AssertionError as e:

            self._handle_generic_error(
                "text_assertion_failure",
                f"文本断言失败: 预期 '{expected_text}' | 实际 '{actual_text}'",
                e,
                ElementAssertionException
            )

        except ElementPlaceException as e:

            self._handle_generic_error(
                "assertion_place_failure",
                f"断言失败-元素定位问题: {place}",
                e,
                ElementAssertionException
            )

    @allure.step('断言元素存在')
    def assert_element_exists(self, place, timeout=2) -> None:
        """
        断言元素存在
        :param place: 元素定位器
        :param timeout: 等待元素的超时时间
        """
        try:
            self.find_element(place, timeout=timeout)
            allure.attach(f"元素存在: {place}", "断言详情", allure.attachment_type.TEXT)

        except ElementPlaceException as e:

            self._handle_generic_error(
                "element_not_exists",
                f"断言元素存在失败: {place}",
                e,
                ElementAssertionException
            )

    @allure.step('断言元素不存在')
    def assert_element_not_exists(self, place, timeout=2) -> None:
        """
        断言元素不存在
        :param place: 元素定位器
        :param timeout: 短超时时间，用于快速验证元素不存在
        """
        try:
            self.find_element(place, timeout=timeout)
            # 如果找到了元素，断言失败
            self._handle_generic_error(
                "element_exists",
                f"断言元素不存在失败: 元素 {place} 存在",
                None,
                ElementAssertionException
            )
        except ElementPlaceException:
            # 元素不存在，断言成功
            allure.attach(f"元素不存在: {place}", "断言详情", allure.attachment_type.TEXT)

